package eva

import (
	"fmt"

	"gitcode.com/deyiyangyang/gascheme/ast"
	"gitcode.com/deyiyangyang/gascheme/obj"
)

// Eval evaluates an expression in the given environment and returns the result
// This is the main function that dispatches to the appropriate evaluation function
// based on the type of the AST node
func Eval(expr ast.Expression, env obj.Environment) obj.Object {
	switch node := expr.(type) {
	case *ast.IntegerLiteral:
		return &obj.Integer{Value: node.Value}
	case *ast.BooleanLiteral:
		return &obj.Boolean{Value: node.Value}
	case *ast.StringLiteral:
		return &obj.String{Value: node.Value}
	case *ast.CharacterLiteral:
		return &obj.Character{Value: node.Value}
	case *ast.RationalLiteral:
		gcd := calculateGcd(node.Numerator, node.Denominator)
		return &obj.Rational{
			Numerator:   node.Numerator / gcd,
			Denominator: node.Denominator / gcd,
		}
	case *ast.RealLiteral:
		return &obj.Real{Value: node.Value}

	case *ast.SymbolLiteral:
		// For symbols, look them up in the environment
		// This is different from some other Lisp dialects where symbols are self-evaluating
		// To get a symbol object, use the quote special form
		val, ok := env.Get(node.Value)
		if !ok {
			return newError("unknown symbol: %s", node.Value)
		}
		return val
	case *ast.ListExpression:
		return evalListExpression(node, env)
	default:
		return newError("unknown node type: %T", node)
	}
}

// evalListExpression evaluates a list expression AST node
// It handles procedure calls, including built-in procedures like +, -, *, /, and define
func evalListExpression(list *ast.ListExpression, env obj.Environment) obj.Object {
	// Check if the list is empty
	if len(list.Elements) == 0 {
		return &obj.List{} // Empty list
	}

	// Special handling for special forms
	if symbol, ok := list.Elements[0].(*ast.SymbolLiteral); ok {
		switch symbol.Value {
		case "define":
			// For define, we need to handle it specially because it needs access to the environment
			args := list.Elements[1:]
			return applyDefine(args, env)
		case "quote":
			// For quote, we return the unevaluated expression as a symbol or list
			args := list.Elements[1:]
			if len(args) != 1 {
				return newError("quote requires exactly 1 argument")
			}
			return quote(args[0])
		case "lambda":
			// For lambda, we create a procedure object
			args := list.Elements[1:]
			return applyLambda(args, env)
		case "let":
			// For let, we transform it into a lambda expression application
			args := list.Elements[1:]
			return applyLet(args, env)
		case "let*":
			// For let*, we transform it into nested let expressions
			args := list.Elements[1:]
			return applyLetStar(args, env)
		case "if":
			// For if, we evaluate the test and choose which branch to evaluate
			args := list.Elements[1:]
			return applyIf(args, env)
		case "cond":
			// For cond, we evaluate clauses until one is true
			args := list.Elements[1:]
			return applyCond(args, env)
		case "begin":
			// For begin, we evaluate expressions in sequence and return the value of the last one
			args := list.Elements[1:]
			if len(args) == 0 {
				return newError("begin requires at least 1 argument")
			}
			var result obj.Object
			for _, expr := range args {
				result = Eval(expr, env)
				if isError(result) {
					return result
				}
			}
			return result
		}
	}

	// Evaluate the first element (operator/function)
	first := Eval(list.Elements[0], env)
	if isError(first) {
		return first
	}

	// Handle special case for builtin functions
	if builtin, ok := first.(*obj.Builtin); ok {
		// Evaluate all arguments
		args := list.Elements[1:]
		return applyBuiltin(builtin.Fn, args, env)
	}

	// Handle user-defined procedures
	if proc, ok := first.(*obj.Procedure); ok {
		// Evaluate all arguments
		args := list.Elements[1:]
		return applyProcedure(proc, args, env)
	}

	return newError("not a procedure: %s", first.Type())
}

// applyBuiltin applies a builtin function to arguments
func applyBuiltin(fn obj.BuiltinFunction, args []ast.Expression, env obj.Environment) obj.Object {
	// For set!, we need special handling - first argument should not be evaluated
	// We identify set! by checking if it has exactly 2 arguments and the first is a symbol literal
	if len(args) == 2 {
		if _, ok := args[0].(*ast.SymbolLiteral); ok {
			// Check if this looks like a set! call by checking if fn is the Set function
			// We'll do this by checking if the function address matches the Set function
			// This is a bit of a hack, but it works for our purposes
			setFnPtr := fmt.Sprintf("%p", Set)
			fnPtr := fmt.Sprintf("%p", fn)
			if setFnPtr == fnPtr {
				// Use our fixed applySet function
				return applySet(args, env)
			}
		}
	}
	
	// For define, we need special handling - first argument should not be evaluated
	if len(args) >= 2 {
		// Check if this looks like a define call by checking if fn is the Define function
		defineFnPtr := fmt.Sprintf("%p", Define)
		fnPtr := fmt.Sprintf("%p", fn)
		if defineFnPtr == fnPtr {
			// Handle(define name value) form - first argument is a symbol literal
			if symbol, ok := args[0].(*ast.SymbolLiteral); ok {
				// Don't evaluate the first argument (symbol) for define!
				// Evaluate the second argument (value)
				evalValue := Eval(args[1], env)
				if isError(evalValue) {
					return evalValue
				}
				
				// Create symbol object from symbol literal
				symbolObj := &obj.Symbol{Value: symbol.Value}
				
				// Call the define function with the symbol object and evaluated value
				return fn([]obj.Object{symbolObj, evalValue}, env)
			} else if list, ok := args[0].(*ast.ListExpression); ok {
				// Handle(define (name arg1 arg2 ...) body) form - first argument is a list
				// For procedure definitions, we need to create a special object that contains
				// the original AST expressions, since we can't recreate them from objects
				
				// Convert the list expression to a list object with symbol objects
				if len(list.Elements) > 0 {
					elements := make([]obj.Object, len(list.Elements))
					for i, elem := range list.Elements {
						if symLit, ok := elem.(*ast.SymbolLiteral); ok {
							elements[i] = &obj.Symbol{Value: symLit.Value}
						} else {
							// For non-symbol elements, evaluate them
							elements[i] = Eval(elem, env)
							if isError(elements[i]) {
								return elements[i]
							}
						}
					}
					
					// Create a proper list from the elements
					var signatureList *obj.List
					if len(elements) == 0 {
						// Empty list
						signatureList = &obj.List{}
					} else {
						// Build the list from right to left
						signatureList = &obj.List{Car: elements[len(elements)-1], Cdr: &obj.List{}}
						
						// Build the rest of the list from right to left
						for i := len(elements) - 2; i >= 0; i-- {
							signatureList = &obj.List{Car: elements[i], Cdr: signatureList}
						}
					}
					
					// Create a special DefineProcedure object to hold the original AST expressions
					defineProcObj := &obj.DefineProcedure{
						Signature: signatureList,
						Body:      args[1:], // The body consists of all expressions after the procedure definition
					}
					
					// Call the define function with the special object and the body
					return fn([]obj.Object{defineProcObj, defineProcObj}, env)
				}
			} else {
				// For other first argument types, evaluate them
				evalFirst := Eval(args[0], env)
				if isError(evalFirst) {
					return evalFirst
				}
				
				// Evaluate the second argument
				evalSecond := Eval(args[1], env)
				if isError(evalSecond) {
					return evalSecond
				}
				
				// Call the define function with the evaluated arguments
				return fn([]obj.Object{evalFirst, evalSecond}, env)
			}
		}
	}
	
	// For quote, we need special handling - argument should not be evaluated
	// Check if this looks like a quote call by checking if fn is the Quote function
	quoteFnPtr := fmt.Sprintf("%p", Quote)
	fnPtr := fmt.Sprintf("%p", fn)
	if quoteFnPtr == fnPtr {
		// Quote requires exactly 1 argument
		if len(args) != 1 {
			return &obj.Error{Message: "quote requires exactly 1 argument"}
		}
		
		// Don't evaluate the argument for quote, convert AST directly to object
		quotedObj := quote(args[0])
		if isError(quotedObj) {
			return quotedObj
		}
		
		// Call the quote function with the quoted object
		return fn([]obj.Object{quotedObj}, env)
	}
	
	// For normal builtin functions, evaluate arguments and call them normally
	evalArgs := make([]obj.Object, len(args))
	for i, arg := range args {
		evalArgs[i] = Eval(arg, env)
		if isError(evalArgs[i]) {
			return evalArgs[i]
		}
	}

	// Call the builtin function with the environment
	return fn(evalArgs, env)
}

// applyProcedure applies a user-defined procedure to arguments
func applyProcedure(proc *obj.Procedure, args []ast.Expression, env obj.Environment) obj.Object {
	// Evaluate all arguments
	evalArgs := make([]obj.Object, len(args))
	for i, arg := range args {
		evalArgs[i] = Eval(arg, env)
		if isError(evalArgs[i]) {
			return evalArgs[i]
		}
	}

	// Create a new environment for the procedure
	// with the parameters bound to the evaluated arguments
	procEnv := obj.NewEnclosedEnvironment(proc.Env)

	if len(evalArgs) != len(proc.Params) {
		return newError("wrong number of arguments: expected %d, got %d", len(proc.Params), len(evalArgs))
	}

	for i, param := range proc.Params {
		procEnv.Set(param, evalArgs[i])
	}

	// Evaluate the procedure body
	var result obj.Object
	for _, expr := range proc.Body {
		result = Eval(expr, procEnv)
		// If遇到错误，立即返回
		if isError(result) {
			return result
		}
	}

	return result
}

// applySet applies the set! special form to modify a variable's value
func applySet(args []ast.Expression, env obj.Environment) obj.Object {
	// Check if we have exactly 2 arguments
	if len(args) != 2 {
		return newError("set! requires exactly 2 arguments")
	}

	// First argument must be a symbol literal
	symbol, ok := args[0].(*ast.SymbolLiteral)
	if !ok {
		return newError("set! first argument must be a symbol")
	}

	// Find the environment where the variable is defined
	targetEnv := env.FindEnvironment(symbol.Value)
	if targetEnv == nil {
		return newError("set!: undefined variable: %s", symbol.Value)
	}

	// Evaluate the value expression
	value := Eval(args[1], env)
	if isError(value) {
		return value
	}

	// Set the value in the environment where the variable was defined
	targetEnv.Set(symbol.Value, value)

	// In Scheme, set! returns undefined behavior
	// We'll return the value that was set
	return value
}

// newError creates a new error object
func newError(format string, a ...interface{}) *obj.Error {
	return &obj.Error{Message: fmt.Sprintf(format, a...)}
}

// isError checks if an object is an error
func isError(obj obj.Object) bool {
	if obj == nil {
		return false
	}
	return obj.Type() == "ERROR"
}

// calculateGcd calculates the greatest common divisor of two integers
func calculateGcd(a, b int64) int64 {
	for b != 0 {
		a, b = b, a%b
	}
	return a
}

// quote converts an AST expression to its corresponding object without evaluation
// This is used to implement the quote special form
func quote(expr ast.Expression) obj.Object {
	switch node := expr.(type) {
	case *ast.IntegerLiteral:
		return &obj.Integer{Value: node.Value}
	case *ast.BooleanLiteral:
		return &obj.Boolean{Value: node.Value}
	case *ast.RationalLiteral:
		gcd := calculateGcd(node.Numerator, node.Denominator)
		return &obj.Rational{
			Numerator:   node.Numerator / gcd,
			Denominator: node.Denominator / gcd,
		}
	case *ast.RealLiteral:
		return &obj.Real{Value: node.Value}
	case *ast.SymbolLiteral:
		// For symbols, return a Symbol object instead of looking it up in the environment
		return &obj.Symbol{Value: node.Value}
	case *ast.ListExpression:
		// For lists, recursively quote each element
		quotedElements := make([]obj.Object, len(node.Elements))
		for i, element := range node.Elements {
			quotedElements[i] = quote(element)
		}
		
		// Create a proper list from the quoted elements
		if len(quotedElements) == 0 {
			// Empty list
			return &obj.List{}
		}
		
		// Build the list from right to left
		resultList := &obj.List{Car: quotedElements[len(quotedElements)-1], Cdr: &obj.List{}}
		
		// Build the rest of the list from right to left
		for i := len(quotedElements) - 2; i >= 0; i-- {
			resultList = &obj.List{Car: quotedElements[i], Cdr: resultList}
		}
		
		return resultList
	default:
		return newError("cannot quote expression of type %T", node)
	}
}

// applyDefine applies the define special form
// This function handles variable and procedure definitions
func applyDefine(args []ast.Expression, env obj.Environment) obj.Object {
	// Check if we have at least 2 arguments
	if len(args) < 2 {
		return &obj.Error{Message: "define requires at least 2 arguments"}
	}

	// Handle(define name value) form - first argument is a symbol
	if nameExpr, ok := args[0].(*ast.SymbolLiteral); ok {
		// Evaluate the value expression
		value := Eval(args[1], env)
		if isError(value) {
			return value
		}

		// In the environment, set the variable
		env.Set(nameExpr.Value, value)
		return value
	}

	// Handle(define (name arg1 arg2 ...) body) form - first argument is a list
	if procDefExpr, ok := args[0].(*ast.ListExpression); ok {
		if len(procDefExpr.Elements) < 1 {
			return &obj.Error{Message: "invalid procedure definition in define"}
		}

		// Check if the procedure name is a symbol
		procNameExpr, ok := procDefExpr.Elements[0].(*ast.SymbolLiteral)
		if !ok {
			return &obj.Error{Message: "procedure name must be a symbol"}
		}

		// Extract parameters
		var params []string
		for i := 1; i < len(procDefExpr.Elements); i++ {
			paramExpr, ok := procDefExpr.Elements[i].(*ast.SymbolLiteral)
			if !ok {
				return &obj.Error{Message: "procedure parameters must be symbols"}
			}
			params = append(params, paramExpr.Value)
		}

		// Create a procedure object
		proc := &obj.Procedure{
			Name:   procNameExpr.Value,
			Params: params,
			Body:   args[1:], // The body consists of all expressions after the procedure definition
			Env:    env,
		}

		// In the environment, set the procedure
		env.Set(procNameExpr.Value, proc)
		return proc
	}

	return &obj.Error{Message: "define expects a symbol or a procedure definition as the first argument"}
}

// applyIf evaluates an if expression
// (if test consequent alternate) or (if test consequent)
func applyIf(args []ast.Expression, env obj.Environment) obj.Object {
	// Check if we have at least 2 arguments (test and consequent)
	// and at most 3 arguments (test, consequent, and alternate)
	if len(args) < 2 || len(args) > 3 {
		return newError("if requires 2 or 3 arguments")
	}

	// Evaluate the test expression
	test := Eval(args[0], env)
	if isError(test) {
		return test
	}

	// Check if test is false (#f)
	testIsFalse := false
	if boolean, ok := test.(*obj.Boolean); ok {
		testIsFalse = !boolean.Value
	}

	// If test is false, evaluate the alternate (if provided) or return nil
	if testIsFalse {
		if len(args) == 3 {
			// Evaluate and return the alternate
			return Eval(args[2], env)
		} else {
			// No alternate provided, return nil
			return nil
		}
	} else {
		// Test is true (not #f), evaluate and return the consequent
		return Eval(args[1], env)
	}
}

// applyCond evaluates a cond expression
// (cond (test1 expr1 ...) (test2 expr2 ...) ... (else expr ...))
func applyCond(args []ast.Expression, env obj.Environment) obj.Object {
	// Check if we have at least one clause
	if len(args) < 1 {
		return newError("cond requires at least one clause")
	}

	// Process each clause
	for i, clauseExpr := range args {
		// Each clause should be a list
		clause, ok := clauseExpr.(*ast.ListExpression)
		if !ok {
			return newError("cond clauses must be lists")
		}

		// Check if the clause has at least one element
		if len(clause.Elements) < 1 {
			return newError("cond clauses must have at least one element")
		}

		// Check if this is the else clause (last clause and starts with else)
		isElseClause := false
		if symbol, ok := clause.Elements[0].(*ast.SymbolLiteral); ok {
			if symbol.Value == "else" && i == len(args)-1 {
				isElseClause = true
			}
		}

		// If it's an else clause, evaluate the expressions and return the last one
		if isElseClause {
			if len(clause.Elements) < 2 {
				return newError("else clause must have at least one expression")
			}
			
			var result obj.Object
			// Evaluate all expressions in the else clause
			for j := 1; j < len(clause.Elements); j++ {
				result = Eval(clause.Elements[j], env)
				if isError(result) {
					return result
				}
			}
			return result
		}

		// Evaluate the test expression
		test := Eval(clause.Elements[0], env)
		if isError(test) {
			return test
		}

		// Check if test is not false (#f)
		testIsTrue := true
		if boolean, ok := test.(*obj.Boolean); ok {
			testIsTrue = boolean.Value
		}

		// If test is true, evaluate the expressions and return the last one
		if testIsTrue {
			if len(clause.Elements) < 2 {
				return newError("cond clause must have at least one expression")
			}
			
			var result obj.Object
			// Evaluate all expressions in the clause
			for j := 1; j < len(clause.Elements); j++ {
				result = Eval(clause.Elements[j], env)
				if isError(result) {
					return result
				}
			}
			return result
		}
	}

	// If no clause matched, return nil
	return nil
}

// applyLetStar implements the let* special form directly
// (let* ((var1 val1) (var2 val2) ...) exp1 exp2 ...)
// Each binding is evaluated in order, and each can refer to previous bindings
func applyLetStar(args []ast.Expression, env obj.Environment) obj.Object {
	// Check if we have at least 2 arguments (bindings and at least one body expression)
	if len(args) < 2 {
		return newError("let* requires at least 2 arguments (bindings and body)")
	}

	// First argument should be a list of bindings
	bindingsList, ok := args[0].(*ast.ListExpression)
	if !ok {
		return newError("let*: first argument must be a list of bindings")
	}

	// Remaining arguments are the body expressions
	body := args[1:]

	// Create a new environment for the let* expression
	letEnv := obj.NewEnclosedEnvironment(env)

	// Process each binding in order
	for _, binding := range bindingsList.Elements {
		bindingList, ok := binding.(*ast.ListExpression)
		if !ok || len(bindingList.Elements) != 2 {
			return newError("let*: each binding must be a list of two elements (variable and value)")
		}

		varExpr, ok := bindingList.Elements[0].(*ast.SymbolLiteral)
		if !ok {
			return newError("let*: binding variable must be a symbol")
		}

		valExpr := bindingList.Elements[1]
		
		// Evaluate the value expression in the current let* environment
		// This allows later bindings to refer to earlier ones
		val := Eval(valExpr, letEnv)
		if isError(val) {
			return val
		}

		// Set the variable in the let* environment
		letEnv.Set(varExpr.Value, val)
	}

	// Evaluate body expressions in the new environment and return the value of the last one
	var result obj.Object
	for _, expr := range body {
		result = Eval(expr, letEnv)
		if isError(result) {
			return result
		}
	}

	return result
}

// applyLet transforms a let expression into a lambda expression application
// (let ((var1 val1) (var2 val2) ...) exp1 exp2 ...) 
// => ((lambda (var1 var2 ...) exp1 exp2 ...) val1 val2 ...)
func applyLet(args []ast.Expression, env obj.Environment) obj.Object {
	// Check if we have at least 2 arguments (bindings and at least one body expression)
	if len(args) < 2 {
		return newError("let requires at least 2 arguments (bindings and body)")
	}

	// First argument should be a list of bindings
	bindingsList, ok := args[0].(*ast.ListExpression)
	if !ok {
		return newError("let: first argument must be a list of bindings")
	}

	// Extract variable names and values from bindings
	var vars []string
	var vals []ast.Expression
	
	for _, binding := range bindingsList.Elements {
		bindingList, ok := binding.(*ast.ListExpression)
		if !ok || len(bindingList.Elements) != 2 {
			return newError("let: each binding must be a list of two elements (variable and value)")
		}
		
		varExpr, ok := bindingList.Elements[0].(*ast.SymbolLiteral)
		if !ok {
			return newError("let: binding variable must be a symbol")
		}
		
		valExpr := bindingList.Elements[1]
		
		vars = append(vars, varExpr.Value)
		vals = append(vals, valExpr)
	}

	// Remaining arguments are the body expressions
	body := args[1:]

	// Create a lambda expression with the body
	lambdaExpr := &ast.ListExpression{
		Elements: make([]ast.Expression, 0, len(body)+2),
	}
	
	// Add lambda symbol
	lambdaExpr.Elements = append(lambdaExpr.Elements, &ast.SymbolLiteral{Value: "lambda"})
	
	// Create parameter list
	paramList := &ast.ListExpression{
		Elements: make([]ast.Expression, len(vars)),
	}
	for i, varName := range vars {
		paramList.Elements[i] = &ast.SymbolLiteral{Value: varName}
	}
	lambdaExpr.Elements = append(lambdaExpr.Elements, paramList)
	
	// Add body expressions
	for _, bodyExpr := range body {
		lambdaExpr.Elements = append(lambdaExpr.Elements, bodyExpr)
	}
	
	// Create application expression
	appExpr := &ast.ListExpression{
		Elements: make([]ast.Expression, 0, len(vals)+1),
	}
	appExpr.Elements = append(appExpr.Elements, lambdaExpr)
	appExpr.Elements = append(appExpr.Elements, vals...)

	// Evaluate the transformed expression
	return Eval(appExpr, env)
}

// applyLambda creates a procedure object from a lambda expression
func applyLambda(args []ast.Expression, env obj.Environment) obj.Object {
	// Check if we have at least 2 arguments (parameters and at least one body expression)
	if len(args) < 2 {
		return newError("lambda requires at least 2 arguments (parameters and body)")
	}

	// First argument should be a list of parameters
	paramsList, ok := args[0].(*ast.ListExpression)
	if !ok {
		return newError("lambda: first argument must be a list of parameters")
	}

	// Extract parameter names
	var params []string
	for _, param := range paramsList.Elements {
		symbol, ok := param.(*ast.SymbolLiteral)
		if !ok {
			return newError("lambda: parameters must be symbols")
		}
		params = append(params, symbol.Value)
	}

	// Remaining arguments are the body expressions (should not be evaluated)
	body := args[1:]

	// Create a procedure object
	// For lambda expressions, we don't have a name, so we'll use "lambda"
	proc := &obj.Procedure{
		Name:   "lambda",
		Params: params,
		Body:   body,
		Env:    env,
	}

	return proc
}
